using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.IO.Packaging;
using System.Xml;
using System.Xml.Serialization;
using System.Text.RegularExpressions;
using System.Net.Mime;

namespace Pg.BioMedics.RatObserver.Gui.Packaging
{
    internal class PackageManager
    {
        #region Constants

        internal const string InfoLocation = "info";
        internal const string ResultLocation = "result";
        internal const string VideoLocation = "video";

        internal const string ResultRelationshipType = "resultRelationship";
        internal const string VideoRelationshipType = "videoRelationship";

        #endregion

        #region Fields

        private readonly string _fileName;

        #endregion

        #region Constructors

        internal PackageManager(string fileName)
        {
            if (!Directory.Exists(Path.GetDirectoryName(fileName)))
                throw new PackagingException("Can't access package, invalid diectory");

            _fileName = fileName;
        }

        #endregion

        #region Methods

        internal void SaveVideo(VideoInfo info, Result result, Stream data)
        {
            // input params validation
            if (info == null)
                throw new PackagingException("Can't save video, info structure not defined");
            else if (result == null)
                throw new PackagingException("Can't save video, results can't be null");
            else if (data == null)
                throw new PackagingException("Can't save video, video stream is null");
            else if (!data.CanRead)
                throw new PackagingException("Can't save video, can't read from video stream");

            try
            {
                using (Package package = ZipPackage.Open(_fileName))
                {
                    // save video info
                    string location = String.Format("{0}/{1}.xml", InfoLocation, info.Id.ToString());
                    Uri infoUri = PackUriHelper.CreatePartUri(new Uri(location, UriKind.RelativeOrAbsolute));
                    PackagePart infoPart = package.CreatePart(infoUri, MediaTypeNames.Text.Xml);

                    using (Stream infoStream = infoPart.GetStream())
                    {
                        using (XmlWriter writer = XmlWriter.Create(infoStream))
                        {
                            XmlSerializer serializer = new XmlSerializer(typeof(VideoInfo));
                            serializer.Serialize(writer, info);
                        }
                    }

                    // save result
                    location = String.Format("{0}/{1}.xml", ResultLocation, info.Id.ToString());
                    Uri resultUri = PackUriHelper.CreatePartUri(new Uri(location, UriKind.RelativeOrAbsolute));
                    PackagePart resultPart = package.CreatePart(resultUri, MediaTypeNames.Text.Xml);

                    using (Stream resultStream = resultPart.GetStream())
                    {
                        using (XmlWriter writer = XmlWriter.Create(resultStream))
                        {
                            XmlSerializer serializer = new XmlSerializer(typeof(Result));
                            serializer.Serialize(writer, result);
                        }
                    }

                    // save video
                    location = String.Format("{0}/{1}.avi", VideoLocation, info.Id.ToString());
                    Uri videoUri = PackUriHelper.CreatePartUri(new Uri(location, UriKind.RelativeOrAbsolute));
                    PackagePart videoPart = package.CreatePart(videoUri, MediaTypeNames.Application.Zip);

                    using (Stream videoStream = videoPart.GetStream())
                    {
                        byte[] buffer = new byte[8192];
                        int length = 0;
                        while ((length = data.Read(buffer, 0, buffer.Length)) != 0)
                            videoStream.Write(buffer, 0, length);
                    }

                    // create relation between items
                    infoPart.CreateRelationship(resultUri, TargetMode.Internal, ResultRelationshipType);
                    infoPart.CreateRelationship(videoUri, TargetMode.Internal, VideoRelationshipType);
                }

            }
            catch (Exception ex)
            {
                throw new PackagingException("Can't save video", ex);
            }
        }

        internal IList<VideoInfo> GetVideoInfos()
        {
            try
            {
                IList<VideoInfo> result = new List<VideoInfo>();
                using (Package package = ZipPackage.Open(_fileName))
                {                   
                    foreach (PackagePart part in package.GetParts())
                    {
                        string pattern = String.Format(@"^/{0}/.*\.{1}$", InfoLocation, "xml");
                        if (Regex.IsMatch(part.Uri.ToString(), pattern))
                        {
                            XmlSerializer serializer = new XmlSerializer(typeof(VideoInfo));
                            using (Stream stream = part.GetStream())
                            {
                                using (XmlReader xmlReader = XmlReader.Create(stream))
                                {
                                    VideoInfo videoInfo = (VideoInfo)serializer.Deserialize(xmlReader);
                                    result.Add(videoInfo);
                                }
                            }
                        }
                    }
                }

                return result;
            }
            catch (Exception ex)
            {
                throw new PackagingException("Can't get video infos", ex);
            }
        }

        internal Result LoadResult(VideoInfo info)
        {
            try
            {
                using (Package package = ZipPackage.Open(_fileName))
                {
                    string infoLocation = String.Format("/{0}/{1}.xml", InfoLocation, info.Id.ToString());
                    foreach (PackagePart part in package.GetParts())
                    {
                        if (part.Uri.ToString() == infoLocation)
                        {
                            foreach (PackageRelationship relationship in part.GetRelationshipsByType(ResultRelationshipType))
                            {
                                Uri resultUri = relationship.TargetUri;
                                PackagePart resultPart = package.GetPart(resultUri);

                                using (Stream stream = resultPart.GetStream())
                                {
                                    using (XmlReader xmlReader = XmlReader.Create(stream))
                                    {
                                        XmlSerializer serializer = new XmlSerializer(typeof(Result));
                                        return (Result)serializer.Deserialize(xmlReader);
                                    }
                                }
                            }
                        }
                    }
                }

                return null;
            }
            catch (Exception ex)
            {
                throw new PackagingException("Can't load results", ex);
            }
        }

        internal TempVideoReference LoadVideo(VideoInfo info)
        {
            try
            {
                using (Package package = ZipPackage.Open(_fileName))
                {
                    string infoLocation = String.Format("/{0}/{1}.xml", InfoLocation, info.Id.ToString());
                    foreach (PackagePart part in package.GetParts())
                    {
                        if (part.Uri.ToString() == infoLocation)
                        {
                            foreach (PackageRelationship relationship in part.GetRelationshipsByType(VideoRelationshipType))
                            {
                                Uri videoUri = relationship.TargetUri;
                                PackagePart videoPart = package.GetPart(videoUri);

                                string fileLocation = Path.Combine(Path.GetTempPath(), String.Format("{0}.avi", info.Id.ToString()));
                                using (FileStream fileStream = File.OpenWrite(fileLocation))
                                {
                                    using (Stream videoStream = videoPart.GetStream())
                                    {
                                        byte[] buffer = new byte[8192];
                                        int length = 0;
                                        while ((length = videoStream.Read(buffer, 0, buffer.Length)) != 0)
                                            fileStream.Write(buffer, 0, length);
                                    }
                                }

                                return new TempVideoReference(fileLocation);
                            }
                        }
                    }
                }

                return null;
            }
            catch (Exception ex)
            {
                throw new PackagingException("Can't load video", ex);
            }
        }

        #endregion
    }
}
